#include "snake.h"

snake Snake;

//Game Data 
int score; //Keeps the count of game score 
int gamedelay;//Lower the game delay faster is the game speed. 
int margem_x_inicial = 50; 
int margem_y_inicial = 50; 
int margem_x_final = 750; 
int margem_y_final = 550; 
int bend_pos = 0; // para saber em k local do array se encontra 
int bend_pos_i = 0;//para saber em k local do array coloca 
int snake_grow = 0; 
Score best_score[10];
char* table;
//videographics 
char *video_mem; 
int modo = 0x103;  
Byte atributol = 0x0A; //= 00010100; 
Byte atributos = 0x02; 
base k = 0xB8000; 
__dpmi_meminfo map; 
int nivel = 0;


Score* scores;


//teclado 
extern void t1_isr(void); 
extern void t0_isr(void); 
extern int time_tick; 
extern int sec_tick; 
Queue q; 

//variaveis da funcao gera comida 
int food_available = 0; 
int foodx, foody; 

//variaveis da funcao de teste ao erro em placa grafica 
int teste_erro = 0; 


/** Funcao responsavel pela inicializacao da cobra (snake).
 * Sao definidas as caracteristicas e os valores iniciais
 */
void initsnake ( ) //Snakes starting coordinate if you modify any one make sure also modify dependent values 
{ 
	int i; 
	Snake.length = 100; //tamanho original 
	Snake.head_x = 200; //posicao inicial x 
	Snake.head_y = 200; //posicao inicial y 
	Snake.head_dir = RIGHT; //inicialmente ela desloca-se para a direita 
	Snake.tail_x = Snake.head_x - Snake.length; //posicao da cauda 
	Snake.tail_y = Snake.head_y; //posicao da cauda 
	Snake.tail_dir = RIGHT; //segue o movimento da cobra 
	for (i = 0; i <1000;i++) // There is no bend initally 
	{ 
		Snake.bend_x[i] = 0; 
		Snake.bend_y[i] = 0;  
		Snake.bend_dir[i] = RIGHT;  
	} 
	Snake.length_p = 100; 
	score = 0; 
	//gamedelay = 5; // Should be change according to game speed need.  
	snake_grow = 0; 
	food_available = 0; 

	bend_pos = 0; // para saber em k local do array se encontra 
	bend_pos_i = 0;//para saber em k local do array coloca 
} 



/** Funcao responsavel pela inicializacao do ecra.
 * Sao definidas as caracteristicas e os valores iniciais (cor de fundo, e dos caracteres)
 */
void initscreen ( ) //Draws Intial Screen. 
{ 
	//inicializacao das variaveis 
	int i; 


	int cor_fundo = BLACK;  

	video_mem = enter_graphics(modo, &map); 

	clear_screen(cor_fundo, video_mem); 

	reset_margin(); // refaz margens
	
	load_char_table(table);
	drawStringAt("Score: ", 90, 30, GREEN, BLACK,
				1, video_mem, table);
	drawIntAt(score, 160, 30, GREEN, BLACK, 1, video_mem, table);
	
	drawStringAt("Nivel: ", 400, 30, GREEN, BLACK,
				1, video_mem, table);
				
	drawIntAt(nivel, 460, 30, GREEN, BLACK, 1, video_mem, table);
	
	//Draw Intial Snake Body  
	for (i = Snake.length; i>0;i--) //This part should be redesigned for change of code of intial values    
	{ 
		set_pixel(Snake.head_x-i,Snake.head_y,YELLOW, video_mem); 
	} 
} 


/** Funcao responsavel pela criacao da area de jogo.
 *  Sao 4 linhas paralelas.
 */
void reset_margin()  
{  
	draw_line(margem_x_inicial, margem_y_inicial, margem_x_inicial, margem_y_final, BLUE, video_mem); 
	draw_line(margem_x_inicial, margem_y_final, margem_x_final, margem_y_final, BLUE, video_mem); 
	draw_line(margem_x_final, margem_y_final, margem_x_final, margem_y_inicial, BLUE, video_mem); 
	draw_line(margem_x_inicial, margem_y_inicial, margem_x_final, margem_y_inicial, BLUE, video_mem); 
} 

/** Funcao que gera um valor aleatorio, entre dois inteiros. */
int randomvalue (int starting, int ending)
{ 
	int answer; 
	time_t t; 

	srand((unsigned) time(&t)); 

	//for(int i = 0; i <previous;i++) rand (); 

	answer = (rand () % (ending-starting)); 
	answer += starting; 
	return answer; 
}  

/** Funcao responsavel pela criacao dos pontos a serem recolhidos pela snake.
 *   Gera aleatoriamente os pontos, no ecra. 
 */
void gera_comida() 
{ 
	if(food_available==1) return; 

	while (!food_available) 
	{ 
		foodx = randomvalue (margem_x_inicial,margem_x_final); 
		foody = randomvalue (margem_y_inicial,margem_y_final); 
		if (get_pixel(foodx,foody, video_mem)== BLACK) 
		{ 
			set_pixel (foodx,foody, WHITE ,video_mem); 
			

			food_available = 1; 
		} 
	} 
} 

/** Funcao que pinta a negro a pontuacao gerada, depois de ser recolhida pela snake */
void apaga_comida() 
{ 
	//variaveis musica 
	//laurindinha song 
	int notas[] = {Sol4,Sol4,La4 ,Sol4,Re4 ,Mi4 ,Fa4 ,Sol4,Sol4,Fa4 ,Mi4 ,Re4 ,Do4 ,Sol4,Sol4,La4 ,Sol4,Re4 ,Mi4 ,Fa4 , 
		Sol4,Sol4,Fa4 ,Mi4 ,Re4 ,Do4 ,Do4 ,Do4 ,Re4 ,Mi4 ,Fa4 ,Fa4 ,Fa4 ,Fa4 ,Mi4 ,Re4 ,Do4 ,Si3 ,Re4 ,Do4 , 
		Fa4 ,Mi4 ,Re4 ,Do4 ,Mi4 ,Re4 ,Re4 ,Re4 ,Mi4 ,Re4 ,Do4 ,Si3 ,Re4 ,Do4 }; 

	
	if(food_available==0) return; 

	if((Snake.head_x == foodx && Snake.head_y == foody)) { 

		food_available = 0; 
		play_note(notas[10],100); 
		snake_grow = 1; 
		score += 8;
		drawStringAt("Score: ", 90, 30, GREEN, BLACK,
				1, video_mem, table);
	drawIntAt(score, 160, 30, GREEN, BLACK, 1, video_mem, table);
	

	} 
} 


/** Funcao responsavel pela leitura das teclas responsaveis pelo
 * direccioamento da snake recorrendo as interrupcoes do teclado. 
 */ 
void userinput ()// This Function changes the direction of motion of snake according to input. 
{ 

	if (bend_pos_i> BEND_SIZE) bend_pos_i = 0; // Makes the bend array a circular queue 

	//scancodes do teclado usados 
	//Left  203 
	//Right         205 
	//Up           200 
	//Down  208 

	char* tc = (char*) malloc (10*sizeof(char*)); 

	while(queueEmpty(&q) != true){ 
		unsigned char scancode = queueGet(&q); 

		// recebe a ultima nova tecla premida e transforma em caracter (teclado QWERTY) 
		tc = keyPressed(scancode); 

		//Change head direction based on logic 
		if(strcmp(tc, "escape") == 0) 
		{ 
			//instalacao da interrupcao teclado 
			_go32_dpmi_seginfo new_vec, starter_irq; 
			reinstall_asm_irq_handler(KBD_IRQ, &new_vec); 
			reinstall_asm_irq_handler(T0_IRQ, &starter_irq); 
			leave_graphics(map); 
			exit(0); 
		} 

		if ((strcmp(tc, "left") == 0) && Snake.head_dir != RIGHT && Snake.head_dir != LEFT) 
		{ 
			Snake.head_dir = LEFT;                           
			Snake.bend_x [bend_pos_i] = Snake.head_x; 
			Snake.bend_y [bend_pos_i] = Snake.head_y; 
			Snake.bend_dir [bend_pos_i] = LEFT; 
			bend_pos_i++; 
		}  

		if ((strcmp(tc, "right")==0) && Snake.head_dir != LEFT && Snake.head_dir != RIGHT) 
		{ 
			Snake.head_dir = RIGHT; 
			Snake.bend_x [bend_pos_i] = Snake.head_x; 
			Snake.bend_y [bend_pos_i] = Snake.head_y; 
			Snake.bend_dir [bend_pos_i] = RIGHT; 
			bend_pos_i++; 
		} 

		if ((strcmp(tc, "up")==0) && Snake.head_dir != DOWN && Snake.head_dir != UP) 
		{ 
			Snake.head_dir = UP; 
			Snake.bend_x [bend_pos_i] = Snake.head_x; 
			Snake.bend_y [bend_pos_i] = Snake.head_y; 
			Snake.bend_dir [bend_pos_i] = UP; 
			bend_pos_i++; 
		} 

		if ((strcmp(tc, "down")==0) && Snake.head_dir != UP && Snake.head_dir != DOWN) 
		{ 
			Snake.head_dir = DOWN;  
			Snake.bend_x [bend_pos_i] = Snake.head_x; 
			Snake.bend_y [bend_pos_i] = Snake.head_y; 
			Snake.bend_dir [bend_pos_i] = DOWN; 
			bend_pos_i++; 
		} 
	}     
} 

/** Funcao responsavel pelo teste de set_pixel em MODO GRAFICO */
void ver_erros()  
{ 
	int i, j; 
	for(i = 0; i< 30; ++i) { 
		for(j = 0; j< 30; ++j) { 
			if(teste_erro==1) 
				set_pixel(i, j,BLUE, video_mem); 
			else 
				set_pixel(i, j,YELLOW, video_mem); 
		} 
	} 
	if(teste_erro==1) 
		teste_erro=0; 
	else 
		teste_erro=1; 
} 


/** Funcao responsavel pela verificacao de colisao de pixeis. Caso exista colisao surge GAME OVER. */
int verifica_colisao() 
{ 
	int verifica_cor = get_pixel(Snake.head_x, Snake.head_y, video_mem); 

	if(verifica_cor == YELLOW) 
	{ 
		leave_graphics(map); //sai do ambiente grafico 
		//game over 
		printStringAt("GAME OVER", 10, 10, atributol); 
		mili_sleep(1000); 
		//gameover(score);
		//exit(0); -> finaliza agora no menu final 
		return 1; 
	} 
	else 
	{ 
		return 0; 
	} 
} 

/** Funcao responsavel pela interpretacao das teclas recebidas pela funcao userinput, e pelo
 * dirrecionamento da cabeca e da cauda, respectivamente. 
 */
void movesnake() //Moves The Snake Across the Screen 
{ 
	if ( bend_pos > BEND_SIZE) bend_pos = 0; 

	//Move the Head -- a funcionar correctamente 
	if (Snake.head_dir == LEFT) 
	{ 
		if(Snake.head_x < margem_x_inicial+1) //condicao para sair de um lado do ecra e entrar do outro 
		{ 
			Snake.head_x = margem_x_final; 

		} 
		else 
		{ 
			Snake.head_x --;  
		} 
	} 
	if (Snake.head_dir == RIGHT)  
	{ 
		if(Snake.head_x > margem_x_final-1) 
		{ 
			Snake.head_x = margem_x_inicial; 
		} 
		else 
		{ 
			Snake.head_x ++;  
		} 
	} 
	if (Snake.head_dir == UP)  
	{ 
		if(Snake.head_y < margem_y_inicial+1) 
		{ 
			Snake.head_y = margem_y_final; 
		} 
		else 
		{ 
			Snake.head_y --; 
		} 
	} 
	if (Snake.head_dir == DOWN)  
	{ 
		if(Snake.head_y > margem_y_final-1) 
		{ 
			Snake.head_y = margem_y_inicial; 
		} 
		else 
		{ 
			Snake.head_y ++;  
		} 
	}  

	if(verifica_colisao() ==1) 
	{ 

		finish(); 
	} 

	//move head               
	set_pixel(Snake.head_x, Snake.head_y,YELLOW, video_mem); 

	//Move the Tail - a funcionar correctamente       
	set_pixel (Snake.tail_x, Snake.tail_y,BLACK, video_mem); 

	//Code to change the y direction at respective time 
	if (Snake.tail_x == Snake.bend_x [bend_pos] && Snake.tail_y == Snake.bend_y [bend_pos]) 
	{ 
		Snake.tail_dir = Snake.bend_dir [bend_pos]; 
		bend_pos++; 
	} 

	if (Snake.tail_dir == LEFT) 
	{ 
		if(Snake.tail_x < margem_x_inicial) 
		{ 
			Snake.tail_x = margem_x_final; 
		} 
		else 
		{ 
			if(snake_grow==0) 
			{ 
				Snake.tail_x --; 
			} 
			else snake_grow = 0; 
		} 
	} 

	if (Snake.tail_dir == RIGHT)  
	{ 
		if(Snake.tail_x > margem_x_final) 
		{ 
			Snake.tail_x = margem_x_inicial; 
		} 
		else 
		{ 
			if(snake_grow==0) Snake.tail_x ++;  
			else snake_grow = 0; 
		} 
	} 

	if (Snake.tail_dir == UP)  
	{ 
		if(Snake.tail_y < margem_y_inicial) 
		{ 
			Snake.tail_y = margem_y_final; 
		} 
		else 
		{ 
			if(snake_grow==0) Snake.tail_y --; 
			else snake_grow = 0; 
		} 
	} 

	if (Snake.tail_dir == DOWN)  
	{ 
		if(Snake.tail_y > margem_y_final) 
		{ 
			Snake.tail_y = margem_y_inicial; 
		} 
		else 
		{ 
			if(snake_grow==0) Snake.tail_y ++;  
			else snake_grow = 0; 
		} 
	} 


} 


void moves() 
{ 
	while (1)  
	{ 
		reset_margin(); 
		movesnake(); 
		userinput(); 
		apaga_comida(); 
		gera_comida(); 
		mili_sleep(gamedelay); 
		//em vez do delay podemos usar o mili_slip ou sec_slip depois de instaladas as interrupcoes do tempo  
	} 
} 


/** Funcao que lanca o menu de acolhimento ao jogador, bem como as instrucoes de jogo */
void start() 
{ 
	//variaveis musica 
	//laurindinha song 
	int notas[] = {Sol4,Sol4,La4 ,Sol4,Re4 ,Mi4 ,Fa4 ,Sol4,Sol4,Fa4 ,Mi4 ,Re4 ,Do4 ,Sol4,Sol4,La4 ,Sol4,Re4 ,Mi4 ,Fa4 , 
		Sol4,Sol4,Fa4 ,Mi4 ,Re4 ,Do4 ,Do4 ,Do4 ,Re4 ,Mi4 ,Fa4 ,Fa4 ,Fa4 ,Fa4 ,Mi4 ,Re4 ,Do4 ,Si3 ,Re4 ,Do4 , 
		Fa4 ,Mi4 ,Re4 ,Do4 ,Mi4 ,Re4 ,Re4 ,Re4 ,Mi4 ,Re4 ,Do4 ,Si3 ,Re4 ,Do4 }; 

	int tempos[] = { 250,750,250,1000,750,250,750,250,750,250,750,250,1000,250,750,250,1000,750,250,750, 
		250,750,250,750,250,1000,125,125,500,500,100,500,500,500,125,125,500,500,1000,1000, 
		125,125,500,500,1000,500,500,500,125,125,500,500,1000,1000 }; 
	int length = sizeof(notas)/sizeof(int); 

	cls(' ', atributos); //limpa ecra 
	mili_sleep(10); 


	char* bem = "BEM VINDO"; 
	printStringAt(bem, 30, 10, atributol); 

	mili_sleep(1000); 

	cls(' ', atributos); 




	desenhaSnake(13,1, atributol); 
	mili_sleep(3000); 

	cls(' ', atributos); 

	char* snake = "     Snake     "; 
	char* instrucao1 = "Seguem as instrucoes para poder comecar a jogar:"; 
	char* instrucao2 = "- A cobra nao podera tocar nela propria, senao o jogador perde."; 
	char* instrucao3 = "- Por cada ponto acumulado o tamanho da cobra e incrementado "; 
	char* instrucao4 = "aumentando assim a dificuldade."; 
	char* instrucao5 = "- Existem 3 niveis diferentes, distinguiveis pela velocidade a que"; 
	char* instrucao6 = "a cobra se movimenta."; 

	printStringAt(snake, 30, 1, atributol); 
	printStringAt(instrucao1, 10, 5, atributol); 
	printStringAt(instrucao2, 10, 7, atributol); 
	printStringAt(instrucao3, 10, 9, atributol); 
	printStringAt(instrucao4, 10, 11, atributol); 
	printStringAt(instrucao5, 10, 13, atributol); 
	printStringAt(instrucao6, 10, 15, atributol); 




	mili_sleep(10000); 

	cls(' ', atributos); 


	char* menu1 = " Escolha a opcao pretendida, ESC para sair."; 
	printStringAt(menu1, 10, 5, atributol); 
	char* menu2 = " 1 - Iniciado "; 
	printStringAt(menu2, 10, 10, atributol); 
	char* menu3 = " 2 - Intermedio "; 
	printStringAt(menu3, 10, 15, atributol); 
	char* menu4 = " 3 - Avancado "; 
	printStringAt(menu4, 10, 20, atributol); 



	char* tc = (char*) malloc (10*sizeof(char*)); 

	//enable_irq(T0_IRQ); 
	//enable_irq(KBD_IRQ); 


	Song musica = {length, 10, notas, tempos}; 


	tc = NULL; 
	char m4[] = {"Escolha uma opcao... "}; 

	int i = 0; 
	while(tc == NULL){ 

		if(i < musica.length) { 
			play_note(musica.notes[i],musica.time[i]*2); 
			delay(musica.pause); 
			++i; 

		} else if(i == musica.length) { 
			printStringAt(m4,10,3,atributol); 
			gotoxy(48,21); 
		} 

		unsigned char scancode; 
		if (queueEmpty(&q) != true) 
		{ 
			scancode = queueGet(&q); 
			// recebe a ultima nova tecla premida e transforma em caracter (teclado QWERTY) 
			tc = keyPressed(scancode); 
		} 


		if(strcmp(tc, "um") == 0 ) 
		{ 
			nivel = 1;
			gamedelay = 20; //atrasa movimento 
			return; 
		} 
		else if(strcmp(tc, "dois") == 0 ) 
		{ 
			nivel = 2;
			gamedelay = 10; //nivel intermedio 
			return; 
		} 
		else if(strcmp(tc, "tres") == 0 ) 
		{ 
			nivel = 3;
			gamedelay = 5; //muito rapido 
			return; 
		} 
		else if(strcmp(tc, "escape") == 0) 
		{ 
			//instalacao da interrupcao teclado 
			_go32_dpmi_seginfo new_vec, starter_irq; 
			reinstall_asm_irq_handler(KBD_IRQ, &new_vec); 
			reinstall_asm_irq_handler(T0_IRQ, &starter_irq); 
			exit(0); 
		} 


	} 
} 

/**Funcao que lanca o menu final, depois de ocorrer GAME OVER. */
void finish() 
{ 
	cls(' ', atributos); //limpa ecra 
	mili_sleep(10); 

	char* menu1 = " Escolha a opcao pretendida: "; 
	char* menu2 = "1. Voltar ao menu inicial; "; 
	char* menu3 = "2. Sair."; 
	printStringAt(menu1, 10, 5, atributol); 
	printStringAt(menu2, 10, 10, atributol); 
	printStringAt(menu3, 10, 15, atributol); 


	char* tc = (char*) malloc (10*sizeof(char*)); 

	RTC_TIME *initTime = (RTC_TIME*) malloc (3*sizeof(Byte)); 
	RTC_TIME *finishTime = (RTC_TIME*) malloc (3*sizeof(Byte)); 
	rtc_read_time(initTime); 

	(*finishTime).hour = (*initTime).hour; 
	(*finishTime).min = (*initTime).min; 
	(*finishTime).sec = (*initTime).sec + 25; 
	if( (*finishTime).sec >= 60 ) { 
		(*finishTime).sec = (*finishTime).sec-60+25; (*finishTime).min++; 
		if((*finishTime).min >= 60) { 
			(*finishTime).min = 0; (*finishTime).hour++; 
			if((*finishTime).hour >= 24) { 
				(*finishTime).hour = 0; (*finishTime).sec = 0; 
			} 
		} 
	} 


	while(1){ 

		unsigned char scancode; 

		RTC_TIME *nowTime = (RTC_TIME*) malloc (3*sizeof(Byte)); 

		rtc_read_time(nowTime); 

		if(((*finishTime).sec < (*nowTime).sec && (*finishTime).min == (*nowTime).min && (*finishTime).hour == (*nowTime).hour) ||  
				((*finishTime).min < (*nowTime).min && (*finishTime).hour == (*nowTime).hour) ||  
				((*finishTime).hour < (*nowTime).hour) ) 
			exit(0); 

		if (queueEmpty(&q) != true) 
		{ 
			scancode = queueGet(&q); 
		} 

		//delay(1000); 
		// recebe a ultima nova tecla premida e transforma em caracter (teclado QWERTY) 
		tc = keyPressed(scancode); 

		if(strcmp(tc, "um") == 0 ) 
		{ 
			playsnake(); //volta a chamar a funcao 
		} 
		else if(strcmp(tc, "dois") == 0 ) 
		{ 
			//instalacao da interrupcao teclado 
			_go32_dpmi_seginfo new_vec, starter_irq; 
			reinstall_asm_irq_handler(KBD_IRQ, &new_vec); 
			reinstall_asm_irq_handler(T0_IRQ, &starter_irq); 
			exit(0); 
		} 

	} 

} 

/** Funcao auxiliar recursiva para inicializar o jogo */
void playsnake() 
{ 

	timer_init(0, LCOM_MODE); 
	timer_load(0, (int)(TIMER_CLK / 1000)); 

	//instalacao da interrupcao teclado 
	_go32_dpmi_seginfo new_vec, starter_irq; 

	//inicializacao da queue 
	queueInit(&q); 

	install_asm_irq_handler(T0_IRQ, t0_isr, &starter_irq); 
	install_asm_irq_handler(KBD_IRQ, t1_isr, &new_vec); 

	//get_best_scores("Pontuacao.txt");
	
	//disable_irq(T0_IRQ); 
	//disable_irq(KBD_IRQ); 
	start();//menu 1 


	
	initsnake(); //iniciar cobra 
	initscreen(); //iniciar ecra jogo 
	moves(); //jogo 


	reinstall_asm_irq_handler(KBD_IRQ, &new_vec); 
	reinstall_asm_irq_handler(T0_IRQ, &starter_irq); 
} 


 /** @} end of interrupts */ 
